<html>
<body onload="RunTest();">
<div id="player_container"></div>
<div id="logs"></div>
</body>

<script type="text/javascript">
// <audio> or <video> player element.
var player;
var tag = '';
var logs = document.getElementById('logs');
var heartbeatCount = 0;
var isSizeTest = false;

function Log(message) {
  logs.innerHTML = message + '<br>' + logs.innerHTML;
  console.log(message);
}

function onLogEvent(e) {
  Log('Event: ' + e.type + ', State: {' + getMediaStatus(e.target) + '}');
}

function getTimeRanges(range) {
  const result = [];
  for (let i = 0; i < range.length; i++) {
    result.push(`[${range.start(i)},${range.end(i)}]`);
  }
  return '[' + result.join(', ') + ']';
}

function getMediaStatus(video) {
  if (video == null) {
    return 'null';
  }
  const result = [];
  result.push(`readyState: ${video.readyState}`);
  result.push(`networkState: ${video.networkState}`);
  result.push(`paused: ${video.paused}`);
  result.push(`ended: ${video.ended}`);
  result.push(`currentTime: ${video.currentTime}`);
  result.push(`duration: ${video.duration}`);
  result.push(`buffered: ${getTimeRanges(video.buffered)}`);
  result.push(`played: ${getTimeRanges(video.played)}`);
  if (tag == 'video') {
    result.push(`size: ${video.videoWidth}x${video.videoHeight}`);
  }
  if (video.error) {
    result.push(`error: {${video.error.code},${video.error.message}}`);
  }
  if (window.internals) {
    result.push(`suspended: ${internals.isMediaElementSuspended(video) ?
      'true' : 'false'}`);
  }
  return result.join(', ');
}

// Listen for |event| from |element|, set document.title = |event| upon event.
function InstallTitleEventHandler(element, event) {
  element.addEventListener(event, function(e) {
    onLogEvent(e);

    if (!isSizeTest) {
      document.title = event.toUpperCase();
      return;
    }

    var video = e.target;
    document.title =
        event.toUpperCase() + ` ${video.videoWidth} ${video.videoHeight}`;
  }, false);
}

function InstallTitleErrorEventHandler(element, error_substr) {
  element.addEventListener('error', function(e) {
    onLogEvent(e);

    // Element's error attribute must be populated.
    var mediaError = element.error;
    if (mediaError == null) {
      Log('ERROR: Null element error attribute while handling error event.');
      Failed();
      return;
    }

    Log('INFO: Element error message is ' + mediaError.message);

    if (error_substr != null && !mediaError.message.includes(error_substr)) {
      Log('ERROR: Element error message (' + mediaError.message +
          ') does not contain expected substring (' + error_substr + ')');
      Failed();
      return;
    }
    document.title = 'ERROR';
  }, false);
}

function Failed() {
  document.title = 'FAILED';
  return false;
}

function SeekTestStep(e) {
  if (e) onLogEvent(e);

  // Remove the previous triggers for this step.
  player.removeEventListener('ended', SeekTestStep, false);
  player.removeEventListener('timeupdate', SeekTestTimeoutSetup, false);

  // Test completes on the next ended event.
  InstallTitleEventHandler(player, 'ended');

  player.currentTime = 0.9 * player.duration;
  Log('Seeking back to ' + 0.9 * player.duration);
  player.play().catch(function(error) {
    Log(error);
  });
}

function SeekTestTimeoutSetup() {
  if (player.currentTime < 2)
    return;

  Log('Triggering seek due to timeout @ ' + player.currentTime);
  SeekTestStep();
}

// Uses URL query parameters to create an audio or video element using a given
// source.  URL must be of the form:
// "player.html?[tag]=[media_url][&loop=[true/false]][&error_substr=substr]".
//
// Plays the media and waits for X seconds of playback or the ended event, at
// which point the test seeks near the end of the file and resumes playback.
// Test completes when the second ended event occurs or an error event occurs at
// any time.
// There is an optional loop query parameter, which when set to true will cause
// the created media element to loop.
// There is an optional error_substr query parameter, which when set will cause
// any error event handling to fail if the MediaError.message does not contain
// the specified substring resulting from decodeURIComponent().
function RunTest() {
  var url_parts = window.location.href.split('?');
  if (url_parts.length != 2)
    return Failed();

  var media_url = '';
  var loop = false;
  var error_substr = null;

  var query_params = url_parts[1].split('&');
  for (var query_param in query_params) {
    var query_parts = query_params[query_param].split('=');
    if (query_parts.length != 2) {
      return Failed();
    }

    if (query_parts[0] == 'audio' || query_parts[0] == 'video') {
      tag = query_parts[0];
      media_url = query_parts[1];
      continue;
    }

    if (query_parts[0] == 'loop') {
      loop = (query_parts[1] == 'true');
      continue;
    }

    if (query_parts[0] == 'error_substr') {
      error_substr = decodeURIComponent(query_parts[1]);
      continue;
    }

    if (query_parts[0] == 'sizetest') {
      isSizeTest = true;
      continue;
    }
  }

  if (tag != 'audio' && tag != 'video') {
    return Failed();
  }

  // Create player and insert into DOM.
  player = document.createElement(tag);
  player.controls = true;
  document.getElementById('player_container').appendChild(player);

  // Transition to the seek test after X seconds of playback or when the ended
  // event occurs, whichever happens first.
  player.addEventListener('ended', SeekTestStep, false);
  player.addEventListener('timeupdate', SeekTestTimeoutSetup, false);

  // Log events.
  player.addEventListener('abort', onLogEvent);
  player.addEventListener('canplay', onLogEvent);
  player.addEventListener('canplaythrough', onLogEvent);
  player.addEventListener('durationchange', onLogEvent);
  player.addEventListener('emptied', onLogEvent);
  player.addEventListener('loadeddata', onLogEvent);
  player.addEventListener('loadedmetadata', onLogEvent);
  player.addEventListener('loadstart', onLogEvent);
  player.addEventListener('play', onLogEvent);
  player.addEventListener('playing', onLogEvent);
  player.addEventListener('progress', onLogEvent);
  player.addEventListener('resize', onLogEvent);
  player.addEventListener('stalled', onLogEvent);
  player.addEventListener('suspend', onLogEvent);
  player.addEventListener('timeupdate', onLogEvent);
  player.addEventListener('waiting', onLogEvent);

  setInterval(function () {
    onLogEvent({'type': 'heartbeat #' + ++heartbeatCount, 'target': player});
  }, 1000);

  // Ensure we percolate up any error events, and optionally verify they contain
  // the expected error substring in their message.
  InstallTitleErrorEventHandler(player, error_substr);

  // Starts the player.
  player.loop = loop;
  player.preload = 'auto';
  player.src = media_url;
  Log('Starting test by calling ' + tag + '.play()');
  player.play().catch(function(error) {
    Log(error);
  });
}
</script>
</html>
